<?php

class Template extends XmlDomConstruct {

    protected $edit = 0;
    protected $id;
    protected $parsed_template_filepath;
    protected $template_name;
    protected $text;
    protected $position;
    protected $value = array();
    protected $class_type;
    public $tinfo;
    protected static $plugins = null;
    // configuration
    protected static $conf = array(
        'checksum' => array(),
        'charset' => 'UTF-8',
        'debug' => FALSE,
        'tpl_dir' => 'templates/',
        'cache_dir' => 'cache/',
        'tpl_ext' => 'html',
        'base_url' => '',
        'php_enabled' => false,
        'template_syntax' => 'Rain',
        'registered_tags' => array(),
        'auto_escape' => FALSE,
        'tags' => array(
            'loop' => array('({loop.*?})', '/{loop="(?<variable>\${0,1}[^"]*)"(?: as (?<key>\$.*?)(?: => (?<value>\$.*?)){0,1}){0,1}}/'),
            'loop_close' => array('({\/loop})', '/{\/loop}/'),
            'if' => array('({if.*?})', '/{if="([^"]*)"}/'),
            'elseif' => array('({elseif.*?})', '/{elseif="([^"]*)"}/'),
            'else' => array('({else})', '/{else}/'),
            'if_close' => array('({\/if})', '/{\/if}/'),
            'noparse' => array('({noparse})', '/{noparse}/'),
            'noparse_close' => array('({\/noparse})', '/{\/noparse}/'),
            'ignore' => array('({ignore}|{\*)', '/{ignore}|{\*/'),
            'ignore_close' => array('({\/ignore}|\*})', '/{\/ignore}|\*}/'),
            'include' => array('({include.*?})', '/{include="([^"]*)"}/'),
            'function' => array('({function.*?})', '/{function="([a-zA-Z_][a-zA-Z_0-9\:]*)(\(.*\)){0,1}"}/'),
            'variable' => array('({\$.*?})', '/{(\$.*?)}/'),
            'constant' => array('({#.*?})', '/{#(.*?)#{0,1}}/'),
        ),
        'sandbox' => true,
        'black_list' => array('exec', 'shell_exec', 'pcntl_exec', 'passthru', 'proc_open', 'system', 'posix_kill', 'posix_setsid', 'pcntl_fork', 'posix_uname', 'php_uname',
            'phpinfo', 'popen', 'file_get_contents', 'file_put_contents', 'rmdir', 'mkdir', 'unlink', 'highlight_contents', 'symlink', 'apache_child_terminate',
            'apache_setenv', 'define_syslog_variables', 'escapeshellarg', 'escapeshellcmd', 'eval', 'fp', 'fput', 'ftp_connect', 'ftp_exec', 'ftp_get',
            'ftp_login', 'ftp_nb_fput', 'ftp_put', 'ftp_raw', 'ftp_rawlist', 'highlight_file', 'ini_alter', 'ini_get_all', 'ini_restore', 'inject_code',
            'mysql_pconnect', 'openlog', 'passthru', 'php_uname', 'phpAds_remoteInfo', 'phpAds_XmlRpc', 'phpAds_xmlrpcDecode', 'phpAds_xmlrpcEncode',
            'posix_getpwuid', 'posix_kill', 'posix_mkfifo', 'posix_setpgid', 'posix_setsid', 'posix_setuid', 'posix_uname', 'proc_close', 'proc_get_status',
            'proc_nice', 'proc_open', 'proc_terminate', 'syslog', 'xmlrpc_entity_decode'
        ),
    );
    protected $template_info = array();

    public function __construct() {//$id) {
        //$this->id = $id;
        //$this->set("url", "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
    }

    public function set($key, $value) {
        $this->value[$key] = $value;
    }

    public function setID($id) {
        $this->id = $id;
    }

    public function setText() {

        $example = new AlaniaContenidosModel();
        $db = new connection();
        if (gettype($this->id) == "string") {

            $example->setNombre($this->id);
        } else {
            $example->setId($this->id);
        }
        if (!count(AlaniaContenidosModel::findByExample($db->db, $example))) {
            //return "Error loading template file ($this->id).<br />";

            return false;
        }

        foreach (AlaniaContenidosModel::findByExample($db->db, $example) as $member) {
            $output = $member->getPlantilla();
            $this->id = $member->getId();
            $this->setTInfo($member->getAlaniaTipoInformacionIdTipoInformacion());
        }
        /* foreach ($this->values as $key => $value) {
          $tagToReplace = "[@$key]";
          $output = str_replace($tagToReplace, $value, $output);
          } */
        $this->text = $this->json2html($output, $this->id);
        return true;
    }

    private function setTInfo($info) {
        $this->tinfo = $info;
    }

    public function getTInfo() {
        return $this->tinfo;
    }

    public function getText() {
        return $this->text;
    }

    private function getArray() {
        return json_decode($this->text, TRUE);
    }

    public function outputPartialHtml($divn) {
        return $this->array2html($divn, $this->getArray());
    }

    public function outputHTML() {
        return $this->json2html($this->text, $this->id);
    }

    public function getId() {
        return $this->id;
    }

    static public function merge($templates, $separator = "\n") {
        $output = "";
        foreach ($templates as $template) {
            $content = (get_class($template) !== "Template") ? "Error, incorrect type - expected Template." : $template->output();
            $output .= $content . $separator;
        }
        return $output;
    }

    public function assign($variable, $value = null) {
        if (is_array($variable)) {
            $this->value += $variable;
        } else {
            $this->value[$variable] = $value;
        }
    }

    public function draw_string($_template_file_name) {

        extract($this->value);
        ob_start();
        try{
        require $this->_check_template($_template_file_name);
        echo ob_get_clean();
        }  catch (RainTpl_NotFoundException $e ){
            $this->send_error($_template_file_name);
        }
    }

    public function draw_for_edit($_template_file_name) {
        $example = new AlaniaContenidosModel();
        $db = new connection();
        if (!intval($_template_file_name)) {// == "string") {
            $example->setNombre($_template_file_name);
        } else {
            $example->setId($_template_file_name);
        }

        //$firephp->log($example->toArray());
        if (!count(AlaniaContenidosModel::findByExample($db->db, $example))) {
            //return "Error loading template file ($this->id).<br />";
            $e = new RainTpl_NotFoundException('Template ' . $_template_file_name . ' not found!');
            throw $e->setTemplateFile($this->id);
        }

        foreach (AlaniaContenidosModel::findByExample($db->db, $example) as $member) {

            $this->setTInfo($member->getAlaniaTipoInformacionIdTipoInformacion());
            return $this->json2html($member->getPlantilla());
        }
    }

    protected function check_loop() {
        
    }

    protected function _check_template($template) {
        $example = new AlaniaContenidosModel();
        $db = new connection();
        if ($template == "static") {
            $example->setId($this->value['para']);
        } else {
            if (gettype($template) == "string") {

                $example->setNombre($template);
            } else {
                $example->setId($template);
            }
        }
        if (!count(AlaniaContenidosModel::findByExample($db->db, $example))) {
            
            //trigger_error("Cannot divide by zero", E_USER_ERROR);
            $e = new RainTpl_NotFoundException('Template ' . $template . ' not found!');
            throw $e->setTemplateFile($this->id);
        } else {
            //var_dump($this->id);
            foreach (AlaniaContenidosModel::findByExample($db->db, $example) as $member) {

                $this->text = $this->json2html($member->getPlantilla());
                $this->id = $member->getId();
                $template_name = $member->getNombre();
                //echo $this->text;

                $parsed_template_filepath = static::$conf['cache_dir'] . $template_name . "." . md5($this->text . implode(static::$conf['checksum'])) . '.rtpl.php';

                if (static::$conf['debug'] || !file_exists($parsed_template_filepath) || ( filemtime($parsed_template_filepath) < $member->getFechaModificacion())) {
                    $this->_compile_string($parsed_template_filepath);
                }
            }
            return $parsed_template_filepath;
        }
    }

    protected function _compile_string($parsed_template_filepath) {

        // open the template
        //$fp = fopen($parsed_template_filepath, "c+");
        //fclose($fp);
        //$fp = fopen($parsed_template_filepath, "r");
        // lock the file
        //if (flock($fp, LOCK_SH)) {
        // xml substitution
        $code = preg_replace("/<\?xml(.*?)\?>/s", "##XML\\1XML##", $this->text);

        // disable php tag
        if (!static::$conf['php_enabled'])
            $code = str_replace(array("<?", "?>"), array("&lt;?", "?&gt;"), $code);

        // xml re-substitution
        $code = preg_replace_callback("/##XML(.*?)XML##/s", function( $match ) {
                    return "<?php echo '<?xml " . stripslashes($match[1]) . " ?>'; ?>";
                }, $code);
        //echo $code;
        $parsed_code = $this->_compile_template($code);
        $parsed_code = '<?php if(!class_exists("Template")){ echo "prueba";/*exit;*/}?>' . $parsed_code;
        //echo $parsed_code;
        // fix the php-eating-newline-after-closing-tag-problem
        $parsed_code = str_replace("?>\n", "?>\n\n", $parsed_code);

        // create directories
        if (!is_dir(static::$conf['cache_dir']))
            mkdir(static::$conf['cache_dir'], 0755, true);

        // check if the cache is writable
        if (!is_writable(static::$conf['cache_dir']))
            throw new RainTpl_Exception('Cache directory ' . static::$conf['cache_dir'] . 'doesn\'t have write permission. Set write permission or set RAINTPL_CHECK_TEMPLATE_UPDATE to false. More details on http://www.raintpl.com/Documentation/Documentation-for-PHP-developers/Configuration/');

        // write compiled file
        //fwrite($fp, $parsed_code);
        file_put_contents($parsed_template_filepath, $parsed_code);
        //file_put_contents($parsed_template_filepath, $parsed_code);
        // release the file lock
        //flock($fp, LOCK_UN);
        //}
        // close the file
        //fclose($fp);
    }

    /**
     * Compile template
     * @access protected
     */
    protected static function get_plugins() {
        if (is_null(static::$plugins)) {
            static::$plugins = new PluginContainer();
        }
        return static::$plugins;
    }

    protected function _compile_template($code) {

        // Execute plugins, before_parse
        $context = $this->get_plugins()->create_context(array(
            'code' => $code,
            'conf' => static::$conf,
                ));
        $this->get_plugins()->run('before_parse', $context);
        $code = $context->code;

        // set tags
        foreach (static::$conf['tags'] as $tag => $tag_array) {
            list( $split, $match ) = $tag_array;

            $tag_split[$tag] = $split;
            $tag_match[$tag] = $match;
        }

        $keys = array_keys(static::$conf['registered_tags']);
        $tag_split += array_merge($tag_split, $keys);


        //split the code with the tags regexp
        $code_split = preg_split("/" . implode("|", $tag_split) . "/", $code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

        //variables initialization
        $parsed_code = $comment_is_open = $ignore_is_open = NULL;
        $open_if = $loop_level = 0;

        //read all parsed code
        while ($html = array_shift($code_split)) {

            //close ignore tag
            if (!$comment_is_open && preg_match($tag_match['ignore_close'], $html))
                $ignore_is_open = FALSE;

            //code between tag ignore id deleted
            elseif ($ignore_is_open) {
                //ignore the code
            }

            //close no parse tag
            elseif (preg_match($tag_match['noparse_close'], $html))
                $comment_is_open = FALSE;

            //code between tag noparse is not compiled
            elseif ($comment_is_open)
                $parsed_code .= $html;

            //ignore
            elseif (preg_match($tag_match['ignore'], $html))
                $ignore_is_open = TRUE;

            //noparse
            elseif (preg_match($tag_match['noparse'], $html))
                $comment_is_open = TRUE;

            //include tag
            elseif (preg_match($tag_match['include'], $html, $matches)) {

                //variables substitution
                $include_var = $this->_var_replace($matches[1], $loop_level);

                //dynamic include
                $parsed_code .= '<?php $tpl = new ' . get_called_class() . ';' .
                        '$tpl_dir_temp = static::$conf[\'tpl_dir\'];' .
                        '$tpl->assign( $this->value );' .
                        (!$loop_level ? null : '$tpl->assign( "key", $key' . $loop_level . ' ); $tpl->assign( "value", $value' . $loop_level . ' );' ) .
                        '$tpl->draw_string("' . $include_var . '");' .
                        '?>';
            }

            //loop
            elseif (preg_match($tag_match['loop'], $html, $matches)) {

                // increase the loop counter
                $loop_level++;
                $var = $this->_var_replace($matches['variable'], $loop_level - 1, $escape = FALSE);
                $counter = "\$counter$loop_level";       // count iteration

                if (isset($matches['key']) && isset($matches['value'])) {
                    $key = $matches['key'];
                    $value = $matches['value'];
                } elseif (isset($matches['key'])) {
                    $key = "\$key$loop_level";               // key
                    $value = $matches['key'];
                } else {
                    $key = "\$key$loop_level";               // key
                    $value = "\$value$loop_level";           // value
                }

                //loop code
                $parsed_code .= "<?php $counter=-1; if( is_array($$var) && sizeof($$var) ) foreach( $$var as $key => $value ){ $counter++; ?>";
            }

            //close loop tag
            elseif (preg_match($tag_match['loop_close'], $html)) {

                //iterator
                $counter = "\$counter$loop_level";

                //decrease the loop counter
                $loop_level--;

                //close loop code
                $parsed_code .= "<?php } ?>";
            }

            //if
            elseif (preg_match($tag_match['if'], $html, $matches)) {

                //increase open if counter (for intendation)
                $open_if++;

                //tag
                $tag = $matches[0];

                //condition attribute
                $condition = $matches[1];

                // check black list
                $this->_black_list($condition);

                //variable substitution into condition (no delimiter into the condition)
                $parsed_condition = $this->_var_replace($condition, $loop_level, $escape = FALSE);
                //firephp=  FirePHP::getInstance(TRUE);
                //firephp->log($condition,"condition");
                //firephp->log($parsed_condition,"parsed condition");
                //if code
                $parsed_code .= "<?php if( $parsed_condition ){ ?>";
            }

            //elseif
            elseif (preg_match($tag_match['elseif'], $html, $matches)) {

                //tag
                $tag = $matches[0];

                //condition attribute
                $condition = $matches[1];

                // check black list
                $this->_black_list($condition);

                //variable substitution into condition (no delimiter into the condition)
                $parsed_condition = $this->_var_replace($condition, $loop_level, $escape = FALSE);

                //elseif code
                $parsed_code .= "<?php }elseif( $parsed_condition ){ ?>";
            }

            //else
            elseif (preg_match($tag_match['else'], $html)) {

                //else code
                $parsed_code .= '<?php }else{ ?>';
            }

            //close if tag
            elseif (preg_match($tag_match['if_close'], $html)) {

                //decrease if counter
                $open_if--;

                // close if code
                $parsed_code .= '<?php } ?>';
            }

            // function
            elseif (preg_match($tag_match['function'], $html, $matches)) {

                // get function
                $function = $matches[1];

                // var replace
                if (isset($matches[2]))
                    $parsed_function = $function . $this->_var_replace($matches[2], $loop_level, $escape = FALSE, $echo = FALSE);
                else
                    $parsed_function = $function . "()";

                // check black list
                $this->_black_list($parsed_function);

                // function 
                $parsed_code .= "<?php echo $parsed_function; ?>";
            }

            //variables
            elseif (preg_match($tag_match['variable'], $html, $matches)) {
                //variables substitution (es. {$title})
                $parsed_code .= "<?php " . $this->_var_replace($matches[1], $loop_level, $escape = TRUE, $echo = TRUE) . "; ?>";
            }

            //constants
            elseif (preg_match($tag_match['constant'], $html, $matches)) {
                $parsed_code .= "<?php echo " . $this->_con_replace($matches[1], $loop_level) . "; ?>";
            }

            // registered tags
            else {

                $found = FALSE;
                foreach (static::$conf['registered_tags'] as $tags => $array) {
                    if (preg_match_all('/' . $array['parse'] . '/', $html, $matches)) {
                        $found = true;
                        $parsed_code .= "<?php echo call_user_func( static::\$conf['registered_tags']['$tags']['function'], " . var_export($matches, 1) . " ); ?>";
                    }
                }

                if (!$found)
                    $parsed_code .= $html;
            }
        }




        // Execute plugins, after_parse
        $context->code = $parsed_code;

        $this->get_plugins()->run('after_parse', $context);

        return $context->code;
    }

    protected function _var_replace($html, $loop_level = NULL, $escape = TRUE, $echo = FALSE) {


        // change variable name if loop level
        if ($loop_level)
            $html = str_replace(array('$value', '$key', '$counter'), array('$value' . $loop_level, '$key' . $loop_level, '$counter' . $loop_level), $html);

        // if it is a variable
        if (preg_match_all('/(\$[a-z_A-Z][\.\[\]\"\'a-zA-Z_0-9]*)/', $html, $matches)) {
//firephp=  FirePHP::getInstance(TRUE);
//firephp->log($html,"html");
            // substitute . and [] with [" "]
            for ($i = 0; $i < count($matches[1]); $i++) {

                $rep = preg_replace('/\[(\${0,1}[a-zA-Z_0-9]*)\]/', '["$1"]', $matches[1][$i]);
                //firephp->log($rep,"rep");
                $rep = preg_replace('/\.(\${0,1}[a-zA-Z_0-9]*)/', '["$1"]', $rep);
                //firephp->log($rep,"rep2");
                $html = str_replace($matches[0][$i], $rep, $html);

                //firephp->log($html,"html2");
            }

            // update modifier
            $html = $this->_modifier_replace($html);

            // if does not initialize a value, e.g. {$a = 1}
            if (!preg_match('/\$.*=.*/', $html)) {

                // escape character
                if (static::$conf['auto_escape'] && $escape)
                //$html = "htmlspecialchars( $html )";
                    $html = "htmlspecialchars( $html, ENT_COMPAT, '" . static::$conf['charset'] . "', FALSE )";

                // if is an assignment it doesn't add echo
                if ($echo)
                    $html = "echo " . $html;
            }
        }

        return $html;
    }

    protected function _con_replace($html) {
        $html = $this->_modifier_replace($html);
        return $html;
    }

    protected function _modifier_replace($html) {

        if ($pos = strrpos($html, "|")) {

            // check black list
            $this->_black_list($html);

            $explode = explode(":", substr($html, $pos + 1));
            $function = $explode[0];
            $params = isset($explode[1]) ? "," . $explode[1] : null;

            $html = $function . "(" . $this->_modifier_replace(substr($html, 0, $pos)) . "$params)";
        }

        return $html;
    }

    protected function _black_list($html) {

        if (!self::$conf['sandbox'] || !self::$conf['black_list'])
            return true;

        if (empty(self::$conf['black_list_preg']))
            self::$conf['black_list_preg'] = '#[\W\s]*' . implode('[\W\s]*|[\W\s]*', self::$conf['black_list']) . '[\W\s]*#';

        // check if the function is in the black list (or not in white list)
        if (preg_match(self::$conf['black_list_preg'], $html, $match)) {

            // find the line of the error
            $line = 0;
            $rows = explode("\n", $this->template_info['code']);
            while (!strpos($rows[$line], $html) && $line + 1 < count($rows))
                $line++;

            // stop the execution of the script
            $e = new RainTpl_SyntaxException('Syntax ' . $match[0] . ' not allowed in template: ' . $this->template_info['template_filepath'] . ' at line ' . $line);
            throw $e->setTemplateFile($this->template_info['template_filepath'])
                    ->setTag($match[0])
                    ->setTemplateLine($line);

            return false;
        }
    }

    public function send_error($function, $position = 'content') {
        $error = str_replace('?', $function, "Página ? No encontrada");
        $log = new Template();
        $log->assign("div", $position);
        $log->assign("error", $error);
        $log->draw_string("error");
    }

}

?>